import java.util.ArrayList;
import java.util.Arrays;
import java.util.HashSet;
import java.util.List;
import java.util.Set;
import java.util.TreeSet;

/**
 * Armazena os dados do jogo , e o estado atual.
 */
public class Ants {
	/** Tamanho mximo para o mapa. */
	public static final int MAX_MAP_SIZE = 256;

	private final int loadTime;

	private final int turnTime;

	public final int rows;

	public final int cols;

	private final int turns;

	private final int viewRadius2;

	private final int attackRadius2;

	private final int spawnRadius2;

	private long turnStartTime;

	private final Ilk map[][];
	
	private final boolean visible[][];
	
	private final Set<Tile> visionOffsets;

	/** Minhas formigas */
	private final Set<Tile> myAnts = new HashSet<Tile>();
	/** Formigas inimigas */
	private final Set<Tile> enemyAnts = new HashSet<Tile>();
	/** Meus formigueiros */
	private final Set<Tile> myHills = new HashSet<Tile>();
	/** Formigueiros de meus inimigos */
	private final Set<Tile> enemyHills = new HashSet<Tile>();
	/** Blocos de comida */
	private final Set<Tile> foodTiles = new HashSet<Tile>();

	private final Set<Order> orders = new HashSet<Order>();

	/**
	 * Creates new {@link Ants} object.
	 * 
	 * @param loadTime
	 *            timeout for initializing and setting up the bot on turn 0
	 * @param turnTime
	 *            timeout for a single game turn, starting with turn 1
	 * @param rows
	 *            Altura do mapa do jogo
	 * @param cols
	 *            Largura do mapa do jogo
	 * @param turns
	 *            Nmero mximo de turnos para o jogo
	 * @param viewRadius2
	 *            Raio da rea de viso para cada formiga
	 * @param attackRadius2
	 *            Rio da rea de ataque(alcance) para cada formiga
	 * @param spawnRadius2
	 *            squared spawn radius of each ant
	 */
	public Ants(int loadTime, int turnTime, int rows, int cols, int turns,
			int viewRadius2, int attackRadius2, int spawnRadius2) {
		this.loadTime = loadTime;
		this.turnTime = turnTime;
		this.rows = rows;
		this.cols = cols;
		this.turns = turns;
		this.viewRadius2 = viewRadius2;
		this.attackRadius2 = attackRadius2;
		this.spawnRadius2 = spawnRadius2;
		map = new Ilk[rows][cols];
		for (Ilk[] row : map) {
			Arrays.fill(row, Ilk.LAND);
		}
		
		visible = new boolean[rows][cols];
        for (boolean[] row : visible) {
            Arrays.fill(row, false);
        }
        // calc vision offsets
        visionOffsets = new HashSet<Tile>();
        int mx = (int)Math.sqrt(viewRadius2);
        for (int row = -mx; row <= mx; ++row) {
            for (int col = -mx; col <= mx; ++col) {
                int d = row * row + col * col;
                if (d <= viewRadius2) {
                    visionOffsets.add(new Tile(row, col));
                }
            }
        }
	}

	private final int LIMIT_OF_RECURSION = 25;

	/**
	 * Retorna um grafo com todas as possibilidades de trajato de uma origem ate
	 * um destino </br> com base no {@link Tile} informado.
	 * 
	 * @return
	 */
	public Node getGrafo(Tile start, Tile end) {
		Node result = new Node(start), tempNode = null;
		int maxIteration = 1000, iteration = 0;
		while (iteration++ < maxIteration
				|| (tempNode != null && tempNode.value != end)) {
			tempNode = new Node();
			for (int i = 0; i < 4; i++) {
				tempNode = new Node(getTile(result.value, Aim.fromNumber(i)));
				result.add(tempNode);
				fillAround(tempNode, 1);
			}// for
		}// while
		return result;
	}

	private void fillAround(Node node, int levelOfRecursion) {
		if (levelOfRecursion >= LIMIT_OF_RECURSION)
			return;
		Node tempNode = null;
		for (int i = 0; i < 4; i++) {
			tempNode = new Node(getTile(node.value, Aim.fromNumber(i)));
			if (tempNode.root != null && node.root != null
					&& tempNode.equals(node.root))
				continue;// para nao voltar para a origem
			node.add(tempNode);
			fillAround(tempNode, levelOfRecursion + 1);
		}
	}
	
	public Tile[] getAround(Tile ant){
		Tile tiles[] = new Tile[4];
		for (int i = 0; i < 4; i++) {
			tiles[i] = getTile(ant, Aim.fromNumber(i));
		}
		return tiles;
	}

	/**
	 * Retorna uma colecao com os tiles ao redor do tile informado que estiverem
	 * disponiveis </br>exceto , o tile
	 * 
	 * @param center
	 * @param oldDirection
	 * @return
	 */
	public Set<Tile> getTilesAround(Tile center) {
		Set<Tile> result = new TreeSet<Tile>();
		for (Aim aim : Aim.values())
			result.add(getTile(center, aim));
		return result;
	}

	/**
	 * retorna a direcao contraria a direcao informada.
	 * 
	 * @param aim
	 * @return Direcao inversa
	 */
	public Aim getReverseDirection(Aim aim) {
		Aim result;
		switch (aim) {
		case NORTH:
			result = Aim.SOUTH;
			break;
		case SOUTH:
			result = Aim.NORTH;
			break;
		case EAST:
			result = Aim.WEST;
			break;
		default:
			result = Aim.EAST;
			break;
		}
		return result;
	}

	public Aim getReverseDirection(Tile ant, Tile target) {
		Aim result;
		List<Aim> directions = getDirections(ant, target);
		if (directions.size() > 0)
			result = directions.get(directions.size() - 1);
		else
			return getRamdomDirection();
		return getReverseDirection(result);
	}

	Aim getRamdomDirection() {
		int direction = (int) (Math.random() * 4);
		switch (direction) {
		case 0:
			return Aim.EAST;
		case 1:
			return Aim.WEST;
		case 2:
			return Aim.SOUTH;
		case 3:
			return Aim.NORTH;
		default:
			return Aim.SOUTH;
		}
	}

	/**
	 * Returns timeout for initializing and setting up the bot on turn 0.
	 * 
	 * @return timeout for initializing and setting up the bot on turn 0
	 */
	public int getLoadTime() {
		return loadTime;
	}

	/**
	 * Returns timeout for a single game turn, starting with turn 1.
	 * 
	 * @return timeout for a single game turn, starting with turn 1
	 */
	public int getTurnTime() {
		return turnTime;
	}

	/**
	 * Returns game map height.
	 * 
	 * @return game map height
	 */
	public int getRows() {
		return rows;
	}

	/**
	 * Returns game map width.
	 * 
	 * @return game map width
	 */
	public int getCols() {
		return cols;
	}

	/**
	 * Returns maximum number of turns the game will be played.
	 * 
	 * @return maximum number of turns the game will be played
	 */
	public int getTurns() {
		return turns;
	}

	/**
	 * Returns squared view radius of each ant.
	 * 
	 * @return squared view radius of each ant
	 */
	public int getViewRadius2() {
		return viewRadius2;
	}

	/**
	 * Returns squared attack radius of each ant.
	 * 
	 * @return squared attack radius of each ant
	 */
	public int getAttackRadius2() {
		return attackRadius2;
	}

	/**
	 * Returns squared spawn radius of each ant.
	 * 
	 * @return squared spawn radius of each ant
	 */
	public int getSpawnRadius2() {
		return spawnRadius2;
	}

	/**
	 * Sets turn start time.
	 * 
	 * @param turnStartTime
	 *            turn start time
	 */
	public void setTurnStartTime(long turnStartTime) {
		this.turnStartTime = turnStartTime;
	}

	/**
	 * Returns how much time the bot has still has to take its turn before
	 * timing out.
	 * 
	 * @return how much time the bot has still has to take its turn before
	 *         timing out
	 */
	public int getTimeRemaining() {
		return turnTime - (int) (System.currentTimeMillis() - turnStartTime);
	}

	/**
	 * Retorna um enum indicando o tipo de bloco para o bloco informado.
	 * 
	 * @param tile
	 *            Bloco com a representao do local para o qual se deseja a
	 *            informao.
	 * 
	 * @return ilk at the <cod>tile</code>
	 */
	public Ilk getIlk(Tile tile) {
		return map[tile.getRow()][tile.getCol()];
	}

	/**
	 * Define o tipo de terreno para o local informado.
	 * 
	 * @param tile
	 *            Bloco indicando a localizao no mapa que ser alterada
	 * @param ilk
	 *            que ser definida para o local <code>tile</code>
	 */
	public void setIlk(Tile tile, Ilk ilk) {
		map[tile.getRow()][tile.getCol()] = ilk;
	}

	/**
	 * Retorna o tipo do local de acordo com a localizao informada e a direcao ,
	 * serve para saber <br/>
	 * qual o tipo de terreno caso seja tomada a direo informada no parmetro.
	 * 
	 * @param tile
	 *            Localizao no mapa do jogo
	 * @param direction
	 *            Direo para a qual se pretende ir
	 * 
	 * @return ilk(Tipo de terreno) para a localizao em <code>direction</code>
	 *         para o bloco <cod>tile</code>
	 */
	public Ilk getIlk(Tile tile, Aim direction) {
		Tile newTile = getTile(tile, direction);
		return map[newTile.getRow()][newTile.getCol()];
	}

	/**
	 * Retorna o proximo bloco caso o <code>Tile</code> siga a direo informada
	 * no parmetro.
	 * 
	 * @param tile
	 *            Localizao no mapa do jogo
	 * @param direction
	 *            Direo observada
	 * 
	 * @return Local para a <code>direction</code> do <cod>tile</code>
	 */
	public Tile getTile(Tile tile, Aim direction) {
		int row = (tile.getRow() + direction.getRowDelta()) % rows;
		if (row < 0) {
			row += rows;
		}
		int col = (tile.getCol() + direction.getColDelta()) % cols;
		if (col < 0) {
			col += cols;
		}
		return new Tile(row, col);
	}

	/**
	 * Retorna uma coleo SET contento todas as minhas formigas.
	 * 
	 * @return Coleo de todas as minhas formigas
	 */
	public Set<Tile> getMyAnts() {
		return myAnts;
	}

	/**
	 * Returns a set containing all enemy ants locations.
	 * 
	 * @return a set containing all enemy ants locations
	 */
	public Set<Tile> getEnemyAnts() {
		return enemyAnts;
	}

	/**
	 * Retorna um objeto SET contendo todos os meus formigueiros.
	 * 
	 * @return Retorna a coleo de todos os meus formigueiros
	 */
	public Set<Tile> getMyHills() {
		return myHills;
	}

	/**
	 * Retorna um objeto SET contendo todos os formigueiros inimigos.
	 * 
	 * @return Coleo de todos os formigueiros inimigos
	 */
	public Set<Tile> getEnemyHills() {
		return enemyHills;
	}

	/**
	 * Retorna um objeto SET com todos os locais de comida.
	 * 
	 * @return a set containing all food locations
	 */
	public Set<Tile> getFoodTiles() {
		return foodTiles;
	}

	/**
	 * Returns all orders sent so far.
	 * 
	 * @return all orders sent so far
	 */
	public Set<Order> getOrders() {
		return orders;
	}

	/**
	 * Calculates distance between two locations on the game map.
	 * 
	 * @param t1
	 *            one location on the game map
	 * @param t2
	 *            another location on the game map
	 * 
	 * @return distance between <code>t1</code> and <code>t2</code>
	 */
	public int getDistance(Tile t1, Tile t2) {
		int rowDelta = Math.abs(t1.getRow() - t2.getRow());
		int colDelta = Math.abs(t1.getCol() - t2.getCol());
		rowDelta = Math.min(rowDelta, rows - rowDelta);// verifica um lado ou
		// outro do mapa
		colDelta = Math.min(colDelta, cols - colDelta);
		return rowDelta * rowDelta + colDelta * colDelta;
	}

	public double getDistanceDouble(Tile start, Tile end) {
		double rowDelta = Math.abs(start.getRow() - end.getRow());
		double colDelta = Math.abs(start.getCol() - end.getCol());
		rowDelta = Math.min(rowDelta, rows - rowDelta);// verifica um lado ou
		// outro do mapa
		colDelta = Math.min(colDelta, cols - colDelta);
		return Math.sqrt(Math.pow(colDelta, 2) + Math.pow(rowDelta, 2));
	}

	/**
	 * Returns one or two orthogonal directions from one location to the
	 * another.
	 * 
	 * @param t1
	 *            one location on the game map
	 * @param t2
	 *            another location on the game map
	 * 
	 * @return orthogonal directions from <code>t1</code> to <code>t2</code>
	 */
	public List<Aim> getDirections(Tile t1, Tile t2) {
		List<Aim> directions = new ArrayList<Aim>();
		if (t1.getRow() < t2.getRow()) {
			if (t2.getRow() - t1.getRow() >= rows / 2) {
				directions.add(Aim.NORTH);
			} else {
				directions.add(Aim.SOUTH);
			}
		} else if (t1.getRow() > t2.getRow()) {
			if (t1.getRow() - t2.getRow() >= rows / 2) {
				directions.add(Aim.SOUTH);
			} else {
				directions.add(Aim.NORTH);
			}
		}
		if (t1.getCol() < t2.getCol()) {
			if (t2.getCol() - t1.getCol() >= cols / 2) {
				directions.add(Aim.WEST);
			} else {
				directions.add(Aim.EAST);
			}
		} else if (t1.getCol() > t2.getCol()) {
			if (t1.getCol() - t2.getCol() >= cols / 2) {
				directions.add(Aim.EAST);
			} else {
				directions.add(Aim.WEST);
			}
		}
		return directions;
	}

	/**
	 * Clears game state information about my ants locations.
	 */
	public void clearMyAnts() {
		myAnts.clear();
	}

	/**
	 * Clears game state information about enemy ants locations.
	 */
	public void clearEnemyAnts() {
		enemyAnts.clear();
	}

	/**
	 * Clears game state information about my hills locations.
	 */
	public void clearMyHills() {
		myHills.clear();
	}

	/**
	 * Clears game state information about enemy hills locations.
	 */
	public void clearEnemyHills() {
		enemyHills.clear();
	}

	/**
	 * Clears game map state information
	 */
	public void clearMap() {
		for (int row = 0; row < rows; row++) {
			for (int col = 0; col < cols; col++) {
				if (map[row][col] != Ilk.WATER) {
					map[row][col] = Ilk.LAND;
				}
			}
		}
	}

	/**
	 * Updates game state information about new ants and food locations.
	 * 
	 * @param ilk
	 *            ilk to be updated
	 * @param tile
	 *            location on the game map to be updated
	 */
	public void update(Ilk ilk, Tile tile) {
		map[tile.getRow()][tile.getCol()] = ilk;
		switch (ilk) {
		case FOOD:
			foodTiles.add(tile);
			break;
		case MY_ANT:
			myAnts.add(tile);
			break;
		case ENEMY_ANT:
			enemyAnts.add(tile);
			break;
		}
	}

	/**
	 * Updates game state information about hills locations.
	 * 
	 * @param owner
	 *            owner of hill
	 * @param tile
	 *            location on the game map to be updated
	 */
	public void updateHills(int owner, Tile tile) {
		if (owner > 0)
			enemyHills.add(tile);
		else
			myHills.add(tile);
	}

	/**
	 * Issues an order by sending it to the system output.
	 * 
	 * @param myAnt
	 *            map tile with my ant
	 * @param direction
	 *            direction in which to move my ant
	 */
	public void issueOrder(Tile myAnt, Aim direction) {
		Order order = new Order(myAnt, direction);
		orders.add(order);
		System.out.println(order);
	}
	 /**
     * Returns true if a location is visible this turn
     *
     * @param tile location on the game map
     *
     * @return true if the location is visible
     */
    public boolean isVisible(Tile tile) {
        return visible[tile.getRow()][tile.getCol()];
    }
	
}
